
#include <BMesh>
#include <BGeometry>
#include "../LineStripe.h"
#include "AdsorbVertexPicker.h"

AdsorbVertexPicker::AdsorbVertexPicker()
{
	_camera = 0;
	_adsorbPoint = true;
	_adsorbLineMiddle = true;
	_adsorbPolygonCenter = true;
}
AdsorbVertexPicker::~AdsorbVertexPicker()
{

}

void AdsorbVertexPicker::setCamera(BCamera* camera)
{
	_camera = camera;
}
BCamera* AdsorbVertexPicker::camera() const
{
	return _camera;
}

void AdsorbVertexPicker::setAdsorbPoint(bool adsorbPoint)
{
	_adsorbPoint = adsorbPoint;
}
bool AdsorbVertexPicker::adsorbPoint() const
{
	return _adsorbPoint;
}

void AdsorbVertexPicker::setAdsorbLineMiddle(bool adsorbLineMiddle)
{
	_adsorbLineMiddle = adsorbLineMiddle;
}
bool AdsorbVertexPicker::adsorbLineMiddle() const
{
	return _adsorbLineMiddle;
}

void AdsorbVertexPicker::setAdsorbPolygonCenter(bool adsorbPolygonCenter)
{
	_adsorbPolygonCenter = adsorbPolygonCenter;
}
bool AdsorbVertexPicker::adsorbPolygonCenter() const
{
	return _adsorbPolygonCenter;
}

BPickHit* AdsorbVertexPicker::pickPlane(const BPlane& plane)
{
	const BLine& line = this->line();
	BVector point;
	if (line.intersect(plane, point))
	{
		BPickHit* hit = new BPickHit(Primitive_Points);
		hit->setRatio(line.ratio(point));
		hit->setPoint(point);
		hit->setNormal(plane.normal());
		return hit;
	}
	return 0;
}

bool AdsorbVertexPicker::pickPoint(BShape* shape, const BLine& line, const BVector& point)
{
	BReal len = line.distance(point);
	BReal pixlen = len / _camera->unit(point * this->matrix());
	return pixlen < 10;
}
bool AdsorbVertexPicker::pickLine(BShape* shape, const BLine& line, const BVector& begin, const BVector& end)
{
	bool result = false;
	if (_adsorbLineMiddle)
	{
		BVector point = (begin + end) / 2;
		if (pickPoint(shape, line, point))
		{
			BPickHit* hit = new BPickHit(Primitive_Points);
			hit->setRatio(line.ratio(point));
			hit->setPoint(point);
			hit->setShape(shape);
			hit->setName("middle");
			hit->setProperty("polygon", BLine(begin, end));
			this->record(hit);
			result = true;
		}
	}
	if (!result && _adsorbPoint)
	{
		BVector point = line.closest(BLine(begin, end));
		BReal len = line.distance(point);
		BReal pixlen = len / _camera->unit(point);
		if (pixlen < 10)
		{
			BPickHit* hit = new BPickHit(Primitive_Points);
			hit->setShape(shape);
			hit->setRatio(line.ratio(point));
			hit->setPoint(point);
			this->record(hit);
			result = true;
		}
		pixlen = line.distance(begin) / _camera->unit(begin);
		if (pixlen < 10)
		{
			BPickHit* hit = new BPickHit(Primitive_Points);
			hit->setShape(shape);
			hit->setRatio(line.ratio(begin));
			hit->setPoint(begin);
			hit->setName("point");
			this->record(hit);
			result = true;
		}
		pixlen = line.distance(begin) / _camera->unit(end);
		if (pixlen < 10)
		{
			BPickHit* hit = new BPickHit(Primitive_Points);
			hit->setShape(shape);
			hit->setRatio(line.ratio(end));
			hit->setPoint(end);
			hit->setName("point");
			this->record(hit);
			result = true;
		}
	}
	return result;
}
bool AdsorbVertexPicker::pickPolygon(BShape* shape, const BLine& line, const BPolygon& polygon)
{
	bool result = false;
	if (_adsorbPoint)
	{
		for (int i = 0; i < polygon.size(); i++)
		{
			const BVector& point = polygon(i);
			if (pickPoint(shape, line, point))
			{
				BPickHit* hit = new BPickHit(Primitive_Points);
				hit->setRatio(line.ratio(point));
				hit->setPoint(point);
				hit->setNormal(polygon.normal());
				hit->setShape(shape);
				hit->setName("point");
				hit->setProperty("polygon", polygon);
				this->record(hit);
				result = true;
			}
		}
	}
	if (!result && _adsorbLineMiddle)
	{
		for (int i = 0; i < polygon.size(); i++)
		{
			BLine edge = polygon.edgeLine(i);
			BVector point = edge.center();
			if (pickPoint(shape, line, point))
			{
				BPickHit* hit = new BPickHit(Primitive_Points);
				hit->setRatio(line.ratio(point));
				hit->setPoint(point);
				hit->setNormal(polygon.normal());
				hit->setShape(shape);
				hit->setName("middle");
				hit->setProperty("polygon", polygon);
				this->record(hit);
				result = true;
			}
		}
	}
	if (!result && _adsorbPolygonCenter)
	{
		BVector center = polygon.center();
		if (pickPoint(shape, line, center))
		{
			BPickHit* hit = new BPickHit(Primitive_Points);
			hit->setRatio(line.ratio(center));
			hit->setPoint(center);
			hit->setNormal(polygon.normal());
			hit->setShape(shape);
			hit->setName("center");
			hit->setProperty("polygon", polygon);
			this->record(hit);
			result = true;
		}
	}
	return result;
}

void AdsorbVertexPicker::reset()
{
	_linePicked = false;
}
void AdsorbVertexPicker::pick(const BLine& line, BNode* node)
{
	BPicker::pick(line, node);
}
void AdsorbVertexPicker::pick(const BLine& line, BShape* shape)
{
	if (LineStripe* lineStripe = dynamic_cast<LineStripe*>(shape))
	{
		const BVectorArray& points = lineStripe->linePoints();
		for (int i = 0; i < points.size() - 1; i++)
		{
			const BVector& p0 = points[i];
			const BVector& p1 = points[i + 1];
			if (pickLine(shape, line, p0, p1))
			{
				_linePicked = true;
				return;
			}
		}
	}
	if (_linePicked == false)
	{
		BPicker::pick(line, shape);
	}
}
void AdsorbVertexPicker::pick(const BLine& line, BMesh* mesh)
{
	bool cullback = mesh->cullback();
	const BVectorArray* vertices = mesh->vertices();
	if (const BFaceArray* faces = mesh->faces())
	{
		BPolygon polygon;
		for (int i = 0; i < faces->size(); i++)
		{
			const BFace& face = (*faces)[i];
			const BVector& va = (*vertices)[face.a()];
			const BVector& vb = (*vertices)[face.b()];
			const BVector& vc = (*vertices)[face.c()];
			BReal ratio = 0;
			if (line.intersect(va, vb, vc, ratio, cullback))
			{
				polygon.reset();
				polygon.append(va);
				polygon.append(vb);
				polygon.append(vc);
				if (!pickPolygon(mesh, line, polygon))
				{
					BPickHit* hit = new BPickHit(Primitive_Points);
					hit->setRatio(ratio);
					hit->setPoint(line.vertex(ratio));
					hit->setNormal(polygon.normal());
					hit->setShape(mesh);
					hit->setIndex(i);
					hit->setProperty("polygon", polygon);
					this->record(hit);
				}
			}
		}
	}
	else
	{
		BPolygon polygon;
		for (int i = 0; i < vertices->size(); i += 3)
		{
			const BVector& va = (*vertices)[i];
			const BVector& vb = (*vertices)[i + 1];
			const BVector& vc = (*vertices)[i + 2];
			BReal ratio = 0;
			if (line.intersect(va, vb, vc, ratio, cullback))
			{
				polygon.reset();
				polygon.append(va);
				polygon.append(vb);
				polygon.append(vc);
				if (!pickPolygon(mesh, line, polygon))
				{
					BPickHit* hit = new BPickHit(Primitive_Points);
					hit->setRatio(ratio);
					hit->setPoint(line.vertex(ratio));
					hit->setNormal(polygon.normal());
					hit->setShape(mesh);
					hit->setIndex(i);
					hit->setProperty("polygon", polygon);
					this->record(hit);
				}
			}
		}
	}
}
void AdsorbVertexPicker::pick(const BLine& line, BGeometry* geometry)
{
	BVectorArray* vertices = geometry->vertices();
	if (!vertices)
		return;
	BPolygon polygon;
	bool cullback = geometry->cullback();
	for (int i = 0; i < geometry->elementCount(); i++)
	{
		BElement* element = geometry->element(i);
		Primitive primitive = element->primitive();
		BIntArray* indices = element->indices();
		if (indices)
		{
			int size = indices->size();
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < size; i += 3)
				{
					int ia = (*indices)[i];
					int ib = (*indices)[i + 1];
					int ic = (*indices)[i + 2];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BReal ratio;
					if (line.intersect(va, vb, vc, ratio, cullback))
					{
						polygon.reset();
						polygon.append(va);
						polygon.append(vb);
						polygon.append(vc);
						if (!pickPolygon(geometry, line, polygon))
						{
							BPickHit* hit = new BPickHit(Primitive_Points);
							hit->setRatio(ratio);
							hit->setPoint(line.vertex(ratio));
							hit->setNormal(polygon.normal());
							hit->setShape(geometry);
							hit->setProperty("polygon", polygon);
							this->record(hit);
						}
						return;
					}
				}
			}
			if (element->primitive() == Primitive_TriangleStrip)
			{
				for (int i = 0; i < size; i++)
				{
					int ia = (*indices)[i];
					int ib = (*indices)[i + 1];
					int ic = (*indices)[i + 2];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BReal ratio = 0;
					if (line.intersect(va, vb, vc, ratio, cullback))
					{
						polygon.reset();
						polygon.append(va);
						polygon.append(vb);
						polygon.append(vc);
						if (!pickPolygon(geometry, line, polygon))
						{
							BPickHit* hit = new BPickHit(Primitive_Points);
							hit->setRatio(ratio);
							hit->setPoint(line.vertex(ratio));
							hit->setNormal(polygon.normal());
							hit->setShape(geometry);
							hit->setProperty("polygon", polygon);
							this->record(hit);
						}
						return;
					}
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < size; i += 4)
				{
					int ia = (*indices)[i];
					int ib = (*indices)[i + 1];
					int ic = (*indices)[i + 2];
					int id = (*indices)[i + 3];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					const BVector& vd = (*vertices)[id];
					BReal ratio = 0;
					if (line.intersect(BQuad(va, vb, vc, vd), ratio, cullback))
					{
						polygon.reset();
						polygon.append(va);
						polygon.append(vb);
						polygon.append(vc);
						polygon.append(vd);
						if (!pickPolygon(geometry, line, polygon))
						{
							BPickHit* hit = new BPickHit(Primitive_Points);
							hit->setRatio(ratio);
							hit->setPoint(line.vertex(ratio));
							hit->setNormal(polygon.normal());
							hit->setShape(geometry);
							hit->setProperty("polygon", polygon);
							this->record(hit);
						}
						return;
					}
				}
			}
			continue;
		}
		const BPieceArray* pieces = element->pieces();
		if (pieces)
		{
			int size = pieces->size();
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < size; i += 3)
				{
					int ia = (*pieces)[i].v();
					int ib = (*pieces)[i + 1].v();
					int ic = (*pieces)[i + 2].v();
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BReal ratio = 0;
					if (line.intersect(va, vb, vc, ratio, cullback))
					{
						polygon.reset();
						polygon.append(va);
						polygon.append(vb);
						polygon.append(vc);
						if (!pickPolygon(geometry, line, polygon))
						{
							BPickHit* hit = new BPickHit(Primitive_Points);
							hit->setRatio(ratio);
							hit->setPoint(line.vertex(ratio));
							hit->setNormal(polygon.normal());
							hit->setShape(geometry);
							hit->setProperty("polygon", polygon);
							this->record(hit);
						}
					}
				}
				continue;
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < size; i += 4)
				{
					int ia = (*pieces)[i].v();
					int ib = (*pieces)[i + 1].v();
					int ic = (*pieces)[i + 2].v();
					int id = (*pieces)[i + 3].v();
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					const BVector& vd = (*vertices)[id];
					BReal ratio;
					if (line.intersect(BQuad(va, vb, vc, vd), ratio, cullback))
					{
						polygon.reset();
						polygon.append(va);
						polygon.append(vb);
						polygon.append(vc);
						polygon.append(vd);
						if (!pickPolygon(geometry, line, polygon))
						{
							BPickHit* hit = new BPickHit(Primitive_Points);
							hit->setRatio(ratio);
							hit->setPoint(line.vertex(ratio));
							hit->setNormal(polygon.normal());
							hit->setShape(geometry);
							hit->setProperty("polygon", polygon);
							this->record(hit);
						}
					}
				}
				continue;
			}

			continue;
		}
		int first = element->first();
		int count = element->count();
		if (element->primitive() == Primitive_Triangles)
		{
			for (int i = first; i < count; i += 3)
			{
				const BVector& va = (*vertices)[i];
				const BVector& vb = (*vertices)[i + 1];
				const BVector& vc = (*vertices)[i + 2];
				BReal ratio = 0;
				if (line.intersect(va, vb, vc, ratio, cullback))
				{
					polygon.reset();
					polygon.append(va);
					polygon.append(vb);
					polygon.append(vc);
					if (!pickPolygon(geometry, line, polygon))
					{
						BPickHit* hit = new BPickHit(Primitive_Points);
						hit->setRatio(ratio);
						hit->setPoint(line.vertex(ratio));
						hit->setNormal(polygon.normal());
						hit->setShape(geometry);
						hit->setProperty("polygon", polygon);
						this->record(hit);
					}
					continue;
				}
			}
			continue;
		}
		if (element->primitive() == Primitive_TriangleStrip)
		{
			for (int i = first; i < count; i++)
			{
				const BVector& va = (*vertices)[i];
				const BVector& vb = (*vertices)[i + 1];
				const BVector& vc = (*vertices)[i + 2];
				BReal ratio = 0;
				if (line.intersect(va, vb, vc, ratio, cullback))
				{
					polygon.reset();
					polygon.append(va);
					polygon.append(vb);
					polygon.append(vc);
					if (!pickPolygon(geometry, line, polygon))
					{
						BPickHit* hit = new BPickHit(Primitive_Points);
						hit->setRatio(ratio);
						hit->setPoint(line.vertex(ratio));
						hit->setNormal(polygon.normal());
						hit->setShape(geometry);
						hit->setProperty("polygon", polygon);
						this->record(hit);
					}
					return;
				}
			}
			continue;
		}
		if (element->primitive() == Primitive_Quads)
		{
			for (int i = first; i < count; i += 4)
			{
				const BVector& va = (*vertices)[i];
				const BVector& vb = (*vertices)[i + 1];
				const BVector& vc = (*vertices)[i + 2];
				const BVector& vd = (*vertices)[i + 3];
				BReal ratio = 0;
				if (line.intersect(BQuad(va, vb, vc, vd), ratio, cullback))
				{
					polygon.reset();
					polygon.append(va);
					polygon.append(vb);
					polygon.append(vc);
					polygon.append(vd);
					if (!pickPolygon(geometry, line, polygon))
					{
						BPickHit* hit = new BPickHit(Primitive_Points);
						hit->setRatio(ratio);
						hit->setPoint(line.vertex(ratio));
						hit->setNormal(polygon.normal());
						hit->setShape(geometry);
						hit->setProperty("polygon", polygon);
						this->record(hit);
					}
					return;
				}
			}
			continue;
		}
	}
}
